﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EmitMapper;
using EmitMapper.MappingConfiguration;

namespace System.Data
{
    public static class EntityMapper
    {
        #region NestedTypes
        private class MapEntity
        {
            public DefaultMapConfig Config { get; set; }
            public Func<string, string, bool> MembersMatcher { get; set; }
            public string[] IgnoreNames { get; set; }
        }
        #endregion

        #region Fields
        private static ConcurrentDictionary<string, MapEntity> _config;
        #endregion

        #region Constructors
        static EntityMapper()
        {
            _config = new ConcurrentDictionary<string, MapEntity>();
        }
        #endregion

        #region MapMethods
        private static string GetKey(Type tFrom, Type tTo)
        {
            return tFrom.GUID.ToString("N") + tTo.GUID.ToString("N");
        }

        private static MapEntity GetMapConfig(string key)
        {
            return _config.GetOrAdd(key, k => new MapEntity()
            {
                Config = new DefaultMapConfig()
            });
        }

        /// <summary>
        /// SetMembersMatcher
        /// </summary>
        /// <typeparam name="TFrom"></typeparam>
        /// <typeparam name="TTO"></typeparam>
        /// <param name="membersNameMatcher"></param>
        /// <param name="postProcessor">postProcessor与skipNull冲突</param>
        public static void SetMembersMatcher<TFrom, TTO>(IDictionary<string, string> membersNameMatcher, Action<TFrom, TTO> postProcessor = null)
        {
            Type tFrom = typeof(TFrom), tTo = typeof(TTO);
            var safeMap = new ConcurrentDictionary<string, string>(membersNameMatcher);
            SetMembersMatcher<TFrom, TTO>((fM, tM) =>
            {
                string val;
                if (safeMap.TryGetValue(fM, out val) && val == tM)
                {
                    return true;
                }
                return fM == tM;
            });
            if (postProcessor != null)
            {
                string key = GetKey(tFrom, tTo);
                var map = GetMapConfig(key);
                map.Config.PostProcess<TTO>((val, state) =>
                {
                    postProcessor((TFrom)state, (TTO)val);
                    return val;
                });
            }
        }
        public static void SetMembersMatcher<TFrom, TTO>(Func<string, string, bool> membersMatcher)
        {
            Type tFrom = typeof(TFrom), tTo = typeof(TTO);
            string key = GetKey(tFrom, tTo);
            var map = GetMapConfig(key);
            map.MembersMatcher = membersMatcher;
            map.Config.MatchMembers(membersMatcher);
        }

        public static void SetIgnoreMembers<TFrom, TTO>(params string[] ignoreNames)
        {
            Type tFrom = typeof(TFrom), tTo = typeof(TTO);
            string key = GetKey(tFrom, tTo);
            var map = GetMapConfig(key);
            map.IgnoreNames = ignoreNames;
            map.Config.IgnoreMembers(tFrom, tTo, ignoreNames);
        }
        #endregion

        #region Methods
        public static TTO Map<TFrom, TTO>(TFrom from, TTO to, bool skipNull = false)
        {
            Type tFrom = typeof(TFrom), tTo = typeof(TTO);
            string key = GetKey(tFrom, tTo);
            var map = GetMapConfig(key);
            var config = map.Config;
            var manager = ObjectMapperManager.DefaultInstance;
            if (skipNull)
            {
                config = new DefaultMapConfig();
                if (map.MembersMatcher != null)
                {
                    config.MatchMembers(map.MembersMatcher);
                }
                var set = new List<string>();
                if (!map.IgnoreNames.IsNullOrEmpty())
                {
                    set.AddRange(map.IgnoreNames);
                }
                var props = from.GetType().GetProperties(PropertyAccess.PropertyBinding);
                set.AddRange(props.Where(p => p.GetValue(from) == null).Select(p => p.Name));
                config.IgnoreMembers(tFrom, tTo, set.ToArray());
                manager = new ObjectMapperManager();
            }
            var mapper = manager.GetMapperImpl(tFrom, tTo, config);
            return (TTO)mapper.Map(from, to, from);
        }
        #endregion
    }
}